# ======================================================================
# Copyright CERFACS (April 2019)
# Contributor: Adrien Suau (adrien.suau@cerfacs.fr)
#
# This software is governed by the CeCILL-B license under French law and
# abiding  by the  rules of  distribution of free software. You can use,
# modify  and/or  redistribute  the  software  under  the  terms  of the
# CeCILL-B license as circulated by CEA, CNRS and INRIA at the following
# URL "http://www.cecill.info".
#
# As a counterpart to the access to  the source code and rights to copy,
# modify and  redistribute granted  by the  license, users  are provided
# only with a limited warranty and  the software's author, the holder of
# the economic rights,  and the  successive licensors  have only limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading,  using, modifying and/or  developing or reproducing  the
# software by the user in light of its specific status of free software,
# that  may mean  that it  is complicated  to manipulate,  and that also
# therefore  means that  it is reserved for  developers and  experienced
# professionals having in-depth  computer knowledge. Users are therefore
# encouraged  to load and  test  the software's  suitability as  regards
# their  requirements  in  conditions  enabling  the  security  of their
# systems  and/or  data to be  ensured and,  more generally,  to use and
# operate it in the same conditions as regards security.
#
# The fact that you  are presently reading this  means that you have had
# knowledge of the CeCILL-B license and that you accept its terms.
# ======================================================================

"""

Some code is taken from
https://stackoverflow.com/questions/49740461/how-to-get-endianness-of-numpy-dtype
in particular the `_endianness_map` variable.
"""

import sys

import numpy

BIG_ENDIAN = "big"
LITTLE_ENDIAN = "little"

_endianness_map = {
    ">": BIG_ENDIAN,
    "<": LITTLE_ENDIAN,
    "=": sys.byteorder,
    "|": "not applicable",
}


def reverse_bits(x: numpy.ndarray, bit_number: int = None):
    bits_num = int(numpy.max(x)).bit_length() if bit_number is None else bit_number
    x_reversed = numpy.zeros_like(x)
    for i in range(bits_num):
        x_reversed = (x_reversed << 1) | x & 1
        x >>= 1
    return x_reversed


def reverse_endian(array: numpy.ndarray) -> numpy.ndarray:
    dim = len(array.shape)

    if dim == 1:
        ridx = reverse_bits(numpy.arange(array.shape[0]))
        return array[ridx]

    elif dim == 2:
        ridx0 = reverse_bits(numpy.arange(array.shape[0]))
        ridx1 = reverse_bits(numpy.arange(array.shape[1]))
        return array[ridx0, :][:, ridx1]

    elif dim == 3:
        ridx0 = reverse_bits(numpy.arange(array.shape[0]))
        ridx1 = reverse_bits(numpy.arange(array.shape[1]))
        ridx2 = reverse_bits(numpy.arange(array.shape[2]))
        return array[ridx0, :, :][:, ridx1, :][:, :, ridx2]

    else:
        raise NotImplementedError("Dimension too high: {}.".format(dim))


def native2little(array: numpy.ndarray) -> numpy.ndarray:
    endianness = _endianness_map[array.dtype.byteorder]
    if endianness == BIG_ENDIAN:
        array = reverse_endian(array)
    return array


def native2big(array: numpy.ndarray) -> numpy.ndarray:
    endianness = _endianness_map[array.dtype.byteorder]
    if endianness == LITTLE_ENDIAN:
        array = reverse_endian(array)
    return array


def native2qlm(array: numpy.ndarray) -> numpy.ndarray:
    return native2big(array)
